package com.zhanghu.backendZray.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhanghu.backendZray.cache.ContextCache;
import com.zhanghu.backendZray.cache.LoginTokenCache;
import com.zhanghu.backendZray.constants.CommonConstant;
import com.zhanghu.backendZray.mapper.UserOnlineTokenMapper;
import com.zhanghu.backendZray.pojo.dto.UserTokenInfo;
import com.zhanghu.backendZray.pojo.entity.User;
import com.zhanghu.backendZray.pojo.entity.UserOnlineToken;
import com.zhanghu.backendZray.service.UserOnlineTokenService;
import com.zhanghu.backendZray.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户token在线状态表 服务实现类
 * </p>
 *
 * @author zhangdong
 * @since 2024-09-05
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class UserOnlineTokenServiceImpl extends ServiceImpl<UserOnlineTokenMapper, UserOnlineToken> implements UserOnlineTokenService {
    private final UserService userService;

    /**
     * 登录后保存用户token以及在线状态，并清除该用户在其他设备上的登录状态
     *
     * @param user
     * @param token
     */
    @Override
    public void saveOrUpdate(User user, String token) {
        LambdaQueryWrapper<UserOnlineToken> queryWrapper = new LambdaQueryWrapper<UserOnlineToken>()
                .eq(UserOnlineToken::getUserId, user.getId());
        UserOnlineToken exist = getOne(queryWrapper);
        String otherDeviceLoginToken = null;
        if (exist == null) {
            exist = new UserOnlineToken();
            exist.setUserId(user.getId());
        } else {
            otherDeviceLoginToken = exist.getLoginToken();
        }
        exist.setLoginToken(token);
        exist.setTokenExpireTime(LocalDateTime.now().plusMinutes(CommonConstant.TOKEN_EXPIRE_MINUTES));
        exist.setOnlineStatus(true);
        saveOrUpdate(exist);
        // 信息保存到cache
        UserTokenInfo userTokenInfo = new UserTokenInfo();
        BeanUtil.copyProperties(user, userTokenInfo);
        userTokenInfo.setLoginToken(token);
        userTokenInfo.setTokenExpireTime(exist.getTokenExpireTime());
        LoginTokenCache.putTokenInfo(token, userTokenInfo);
        if (otherDeviceLoginToken != null) {
            LoginTokenCache.deleteToken(otherDeviceLoginToken);
        }
    }

    /**
     * token 是否过期
     *
     * @param token
     * @return
     */
    public boolean tokenExpire(String token) {
        LambdaQueryWrapper<UserOnlineToken> queryWrapper = new LambdaQueryWrapper<UserOnlineToken>()
                .eq(UserOnlineToken::getLoginToken, token);
        UserOnlineToken exist = getOne(queryWrapper);
        if (exist == null) {
            return true;
        }
        LocalDateTime tokenExpireTime = exist.getTokenExpireTime();
        if (tokenExpireTime.isBefore(LocalDateTime.now())) {
            return true;
        }
        return false;
    }

    public boolean tokenExpire(UserOnlineToken userOnlineToken) {
        if (userOnlineToken == null) {
            return true;
        }
        LocalDateTime tokenExpireTime = userOnlineToken.getTokenExpireTime();
        if (tokenExpireTime.isBefore(LocalDateTime.now())) {
            return true;
        }
        return false;
    }

    /**
     * 由token查上下文user信息
     */
    @Override
    public UserTokenInfo getUserTokenInfoByToken(String token) {
        // 会确保这个cache里包含全量的token未过期的用户
        UserTokenInfo tokenInfo = LoginTokenCache.getTokenInfo(token);
        if (tokenInfo == null) {
            return null;
        }
        LocalDateTime tokenExpireTime = tokenInfo.getTokenExpireTime();
        // token已过期
        if (tokenExpireTime.isBefore(LocalDateTime.now())) {
            return null;
        }
        return tokenInfo;
        // 不从db里查
       /* User user = getUserInfoByToken(token);
        if (user == null) {
            return null;
        }
        UserTokenInfo userTokenInfo = new UserTokenInfo();
        BeanUtil.copyProperties(user, userTokenInfo);
        return userTokenInfo;*/
    }

    public User getUserInfoByToken(String token) {
        LambdaQueryWrapper<UserOnlineToken> queryWrapper = new LambdaQueryWrapper<UserOnlineToken>()
                .eq(UserOnlineToken::getLoginToken, token);
        UserOnlineToken exist = getOne(queryWrapper);
        if (exist == null) {
            return null;
        }
        if (tokenExpire(exist)) {
            return null;
        }
        Integer userId = exist.getUserId();
        User user = userService.getById(userId);
        if (user == null) {
            return null;
        }
        return user;
    }

    /**
     * 获取所有token未过期的上下文user信息
     */
    public List<UserTokenInfo> listUserTokenNoExpire2TokenInfo() {
        LambdaQueryWrapper<UserOnlineToken> queryWrapper = new LambdaQueryWrapper<UserOnlineToken>()
                .gt(UserOnlineToken::getTokenExpireTime, LocalDateTime.now());
        List<UserOnlineToken> list = list(queryWrapper);
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        List<Integer> userIdList = list.stream().map(UserOnlineToken::getUserId).collect(Collectors.toList());
        List<User> users = userService.listByIds(userIdList);
        Map<Integer, User> userMap = users.stream().collect(Collectors.toMap(User::getId, Function.identity()));
        List<UserTokenInfo> tokenInfoList = list.stream().map(r -> {
            Integer userId = r.getUserId();
            User user = userMap.get(userId);
            UserTokenInfo userTokenInfo = new UserTokenInfo();
            BeanUtil.copyProperties(user, userTokenInfo);
            userTokenInfo.setLoginToken(r.getLoginToken());
            userTokenInfo.setTokenExpireTime(r.getTokenExpireTime());
            return userTokenInfo;
        }).collect(Collectors.toList());
        return tokenInfoList;
    }

    /**
     * 更新用户状态为在线，并为token续期7天
     */
    @Override
    public void updateUserStatusAndTokenRenewal(Integer userId) {
        LambdaQueryWrapper<UserOnlineToken> queryWrapper = new LambdaQueryWrapper<UserOnlineToken>()
                .eq(UserOnlineToken::getUserId, userId);
        UserOnlineToken userOnlineToken = getOne(queryWrapper);
        if (userOnlineToken == null) {
            log.error("更新用户状态, token续期, 数据表中没有用户数据, id:{}", ContextCache.currentUserId());
            return;
        }
        LocalDateTime newExpireTime = LocalDateTime.now().plusMinutes(CommonConstant.TOKEN_EXPIRE_MINUTES);
        LambdaUpdateWrapper<UserOnlineToken> updateWrapper = new LambdaUpdateWrapper<UserOnlineToken>()
                .eq(UserOnlineToken::getId, userOnlineToken.getId())
                .set(UserOnlineToken::getOnlineStatus, (byte) 1)
                .set(UserOnlineToken::getTokenExpireTime, newExpireTime)
                .set(UserOnlineToken::getOnlineLastCheckTime, LocalDateTime.now());
        update(updateWrapper);
        LoginTokenCache.updateTokenExpireTime(userOnlineToken.getLoginToken(), newExpireTime);
    }

    /**
     * 用户退出登录
     */
    @Override
    public void logout() {
        // 状态置为离线, token效期置为当前时间-10分钟
        LambdaQueryWrapper<UserOnlineToken> queryWrapper = new LambdaQueryWrapper<UserOnlineToken>()
                .eq(UserOnlineToken::getUserId, ContextCache.currentUserId());
        UserOnlineToken userOnlineToken = getOne(queryWrapper);
        if (userOnlineToken == null) {
            return;
        }
        userOnlineToken.setOnlineStatus(false);
        userOnlineToken.setTokenExpireTime(LocalDateTime.now().minusMinutes(10));
        updateById(userOnlineToken);

        // 移除token缓存
        LoginTokenCache.deleteToken(userOnlineToken.getLoginToken());
        log.info("用户退出登录, 状态置为离线, token效期置为当前时间-10分钟, 移除token缓存, userInfo:{}", JSON.toJSONString(ContextCache.currentUser()));
    }

    /**
     * 用户在线状态定期check
     * 5分钟不在线，记为离线
     */
    @Override
    public void loginStatusCheck() {
        log.info("用户在线状态定期check, 开始...");
        LambdaQueryWrapper<UserOnlineToken> queryWrapper = new LambdaQueryWrapper<UserOnlineToken>()
                .eq(UserOnlineToken::getOnlineStatus, (byte) 1);
        List<UserOnlineToken> list = list(queryWrapper);
        if (CollUtil.isEmpty(list)) {
            return;
        }
        List<UserOnlineToken> needUpdateList = new ArrayList<>(list.size());
        for (UserOnlineToken userOnlineToken : list) {
            LocalDateTime onlineLastCheckTime = userOnlineToken.getOnlineLastCheckTime();
            Duration between = Duration.between(onlineLastCheckTime, LocalDateTime.now());
            long minutes = between.toMinutes();
            if (Math.abs(minutes) >= CommonConstant.USER_OFFLINE_MINUTES) {
                userOnlineToken.setOnlineStatus(false);
                needUpdateList.add(userOnlineToken);
            }
        }
        if (CollUtil.isNotEmpty(needUpdateList)) {
            updateBatchById(needUpdateList);
            log.info("用户在线状态定期check, 成功更新:{}", JSON.toJSONString(needUpdateList));
        }
        log.info("用户在线状态定期check, 结束...");
    }

    /**
     * 用户下线（超管操作）
     * 1. 用户状态改为离线
     * 2. token效期改为失效
     * 3. token缓存 移除
     */
    @Override
    public void offline(int id) {
        List<UserOnlineToken> list = listByUserIdIn(Arrays.asList(id));
        if (CollUtil.isEmpty(list)) {
            return;
        }
        UserOnlineToken onlineToken = list.get(0);
        onlineToken.setOnlineStatus(false);
        onlineToken.setTokenExpireTime(LocalDateTime.now().minusMinutes(5));
        updateById(onlineToken);
        LoginTokenCache.deleteToken(onlineToken.getLoginToken());
    }

    public List<UserOnlineToken> listByUserIdIn(List<Integer> userIdList) {
        return list(new LambdaQueryWrapper<UserOnlineToken>().in(UserOnlineToken::getUserId, userIdList));
    }
}
